/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.ycy;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
//import lombok.RequiredArgsConstructor;
//import com.enn.apm.agent.core.boot.AgentPackageNotFoundException;
//import com.enn.apm.agent.core.boot.AgentPackagePath;
//import com.enn.apm.agent.core.boot.PluginConfig;
//import com.enn.apm.agent.core.conf.SnifferConfigInitializer;
//import com.enn.apm.agent.core.logging.api.ILog;
//import com.enn.apm.agent.core.logging.api.LogManager;
//import com.enn.apm.agent.core.plugin.PluginBootstrap;

/**
 * The <code>AgentClassLoader</code> represents a classloader, which is in charge of finding plugins and interceptors.
 */
public class AgentClassLoader extends ClassLoader {

  static {
    /*
     * Try to solve the classloader dead lock. See https://github.com/apache/apm/pull/2016
     */
    registerAsParallelCapable();
  }

//    private static final ILog logger = LogManager.getLogger(AgentClassLoader.class);
  /**
   * The default class loader for the agent.
   */
  private static AgentClassLoader DEFAULT_LOADER;

  private List<File> classpath;
  private List<Jar> allJars;
  private ReentrantLock jarScanLock = new ReentrantLock();

  public static AgentClassLoader getDefault() {
    return DEFAULT_LOADER;
  }

  //    /**
//     * Init the default class loader.
//     *
//     * @throws AgentPackageNotFoundException if agent package is not found.
//     */
//    public static void initDefaultLoader() throws AgentPackageNotFoundException {
//        if (DEFAULT_LOADER == null) {
//            synchronized (AgentClassLoader.class) {
//                if (DEFAULT_LOADER == null) {
//                    DEFAULT_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader());
//                }
//            }
//        }
//    }
//
//    public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException {
//public AgentClassLoader(ClassLoader parent)     {
//        super(parent);
  public AgentClassLoader() {
    File agentDictionary = AgentPackagePath.getPath();
    agentDictionary = new File("/data");
    classpath = new LinkedList<>();
//  classpath.add(new File(agentDictionary, "plugins"));
//  classpath.add(new File(agentDictionary, "activations"));
    System.out.println(agentDictionary);
    classpath.add(new File(agentDictionary + "/springboot-log4j.jar!", "/BOOT-INF/lib"));
    classpath.add(new File(agentDictionary, "/BOOT-INF/lib"));
    classpath.add(agentDictionary);
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
//        List<Jar> allJars = getAllJars();
//        String path = name.replace('.', '/').concat(".class");
//        for (Jar jar : allJars) {
//            JarEntry entry = jar.jarFile.getJarEntry(path);
//            if (entry == null) {
//                continue;
//            }
//            try {
//                URL classFileUrl = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + path);
//                byte[] data;
//                try (final BufferedInputStream is = new BufferedInputStream(
//                    classFileUrl.openStream()); final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
//                    int ch;
//                    while ((ch = is.read()) != -1) {
//                        baos.write(ch);
//                    }
//                    data = baos.toByteArray();
//                }
//                return processLoadedClass(defineClass(name, data, 0, data.length));
//            } catch (IOException e) {
////                logger.error(e, "find class fail.");
//            }
//        }
//        throw new ClassNotFoundException("Can't find " + name);
    return null;
  }

  @Override
  protected URL findResource(String name) {
    List<Jar> allJars = getAllJars();
    for (Jar jar : allJars) {
      JarEntry entry = jar.jarFile.getJarEntry(name);
      if (entry != null) {
        try {
          return new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name);
        } catch (MalformedURLException ignored) {
        }
      }
    }
    return null;
  }

  @Override
  protected Enumeration<URL> findResources(String name) throws IOException {
    List<URL> allResources = new LinkedList<>();
    List<Jar> allJars = getAllJars();
    for (Jar jar : allJars) {
      JarEntry entry = jar.jarFile.getJarEntry(name);
      if (entry != null) {
        allResources.add(new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name));
      }
    }

    final Iterator<URL> iterator = allResources.iterator();
    return new Enumeration<URL>() {
      @Override
      public boolean hasMoreElements() {
        return iterator.hasNext();
      }

      @Override
      public URL nextElement() {
        return iterator.next();
      }
    };
  }

//    private Class<?> processLoadedClass(Class<?> loadedClass) {
//        final PluginConfig pluginConfig = loadedClass.getAnnotation(PluginConfig.class);
//        if (pluginConfig != null) {
//            // Set up the plugin config when loaded by class loader at the first time.
//            // Agent class loader just loaded limited classes in the plugin jar(s), so the cost of this
//            // isAssignableFrom would be also very limited.
//            SnifferConfigInitializer.initializeConfig(pluginConfig.root());
//        }
//
//        return loadedClass;
//    }

  public List<Jar> getAllJars() {
    if (allJars == null) {
      jarScanLock.lock();
      try {
        if (allJars == null) {
          allJars = doGetJars();
        }
      } finally {
        jarScanLock.unlock();
      }
    }

    return allJars;
  }

  public static void main(String[] args) throws IOException {
    LinkedList<Jar> jars = new LinkedList<>();

    String name = "org.apache.logging.log4j.core.impl.Log4jContextFactory";
    String path = name.replace('.', '/').concat(".class");

    JarFile jarFile = null;
    try {
      jarFile = new JarFile("/data/springboot-log4j.jar");
    } catch (IOException e) {
      e.printStackTrace();
    }

    Enumeration<JarEntry> entries = jarFile.entries();

    while (entries.hasMoreElements()) {
      JarEntry jarEntry = entries.nextElement();
      String innerPath = jarEntry.getName();
      if (innerPath.startsWith("BOOT-INF/lib/") && innerPath.endsWith("jar")) {
        //BOOT-INF/lib/spring-boot-starter-json-2.1.6.RELEASE.jar
        System.out.println(jarEntry.getName());
      }
    }

//    jar包中文件URL有专用格式的：jar:<url>!/{entry}
//    例如：jar:file:/.../.../xxx.jar!/.../.../zzz.class

    try {
      URL url = new URL("jar", null, 0, "file:" + "/data/springboot-log4j.jar" + "!/" + "BOOT-INF/lib/" + "log4j-core-2.13.3.jar");
      System.out.println(url);
      URLConnection con = url.openConnection();
      if (con instanceof JarURLConnection) {
        JarURLConnection result = (JarURLConnection) con;
        JarInputStream jarInputStream = new JarInputStream(result.getInputStream());
        JarEntry entry;
        while ((entry = jarInputStream.getNextJarEntry()) != null) {
          System.out.println(entry.getName());
        }
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
    }


  }

  private LinkedList<Jar> doGetJars() {
    LinkedList<Jar> jars = new LinkedList<>();
    for (File path : classpath) {
      System.out.println(path);
      if (path.exists()) {
        String[] jarFileNames = path.list((dir, name) -> name.endsWith(".jar"));
        for (String fileName : jarFileNames) {
          try {
            File file = new File(path, fileName);
            Jar jar = new Jar(new JarFile(file), file);
            jars.add(jar);
            System.out.println(file);
//                        logger.info("{} loaded.", file.toString());
          } catch (IOException e) {
//                        logger.error(e, "{} jar file can't be resolved", fileName);
            e.printStackTrace();
          }
        }
      }
    }
    return jars;

  }

}
